#!/usr/bin/env python
#
# Copyright 2012 Google Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#        
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Author: Marc Cohen
#

'''Implementation of prediction request API.'''

import httplib2
import json
import logging
import urllib
import pickle
import jinja2
import os
from apiclient.discovery import build
import webapp2
from oauth2client.appengine import CredentialsModel
from oauth2client.appengine import StorageByKeyName
from oauth2client.file import Storage
from google.appengine.api import memcache
from google.appengine.api import users
from google.appengine.ext.webapp.util import login_required
from oauth2client.appengine import StorageByKeyName
from oauth2client.appengine import CredentialsModel
from oauth2client.client import OAuth2WebServerFlow

# NOTE: You need to edit the redirect URI into the Dev Console, and that value 
# needs to be updated when you move from running this app locally to running
# on Google App Engine (else you'll get a 'redirect_uri_mismatch' error).

# Static configuation constants.
SCOPE = ('https://www.googleapis.com/auth/devstorage.read_write ' +
         'https://www.googleapis.com/auth/prediction')
USER_AGENT = 'try-prediction/1.0'
SECRETS_FILE = 'rc/client_secrets.json'


jinja_env = jinja2.Environment(loader=jinja2.FileSystemLoader(os.path.join(os.path.dirname(__file__), 'templates')))

def parse_json_file(file):
  '''Utility function to open, read, and parse the contents of
     a file containing text encoded as a JSON document, and
     return resulting json object to caller.
  '''
  f = open(file, 'r')
  json_str = f.read()
  f.close()
  return json.loads(json_str)

class BackendPage(webapp2.RequestHandler):
  '''This class renders the main home page for the "Try Prediction" app.'''

  def post(self):
    '''Use the same logic for posts and gets.'''
    self.get()

  def get(self):
    '''Process get requests.'''
    user = users.get_current_user()

    # Fetch stored server credentials.
    logging.info(USER_AGENT)
    credentials = StorageByKeyName(CredentialsModel, USER_AGENT,
                                   'credentials').locked_get()

    # If server credentials not found, trigger OAuth2.0 web server flow.
    if not credentials or credentials.invalid:
      # If no credentials and no user logged in, redirect to the reset, 
      # which will force a login to make sure this user has permission
      # to initialize the shared server credentials.
      if not user:
        self.redirect("/reset/")

      # Read and parse client secrets JSON file.
      secrets = parse_json_file(SECRETS_FILE)

      client_id = secrets['installed']['client_id']
      client_secret = secrets['installed']['client_secret']

      flow = OAuth2WebServerFlow(client_id=client_id,
                                 client_secret=client_secret,
                                 scope=SCOPE,
                                 user_agent=USER_AGENT,
                                 access_type = 'offline',
                                 approval_prompt='force')
      callback = self.request.relative_url('/backend/auth_return')
      authorize_url = flow.step1_get_authorize_url(callback)

      # Save flow object in memcache for later retrieval on OAuth callback,
      # and redirect this session to Google's OAuth 2.0 authorization site.
      #logging.info('saving flow for user ' + user.user_id())
      memcache.set(user.user_id(), pickle.dumps(flow))
      self.redirect(authorize_url)

    # Set django template and render home page.
    template_values = {
      'user' : str(user)
    }
    template = jinja_env.get_template('home.html')
    self.response.out.write(template.render(template_values))

class AuthHandler(webapp2.RequestHandler):
  '''This class fields OAuth 2.0 web callback for the "Try Prediction" app.'''

  @login_required
  def get(self):
    user = users.get_current_user()

    # Retrieve flow object from memcache.
    logging.info('retrieving flow for user ' + user.user_id())
    flow = pickle.loads(memcache.get(user.user_id()))
    if flow:
      # Extract newly acquired server credentials, store creds
      # in datatore for future retrieval and redirect session
      # back to app's main page.
      credentials = flow.step2_exchange(self.request.params)
      StorageByKeyName(CredentialsModel, USER_AGENT,
                       'credentials').locked_put(credentials)
      self.redirect('/backend/')
    else:
      raise('unable to obtain OAuth 2.0 credentials')

class Reset(webapp2.RequestHandler):
  '''This class processes requests to reset the server's OAuth 2.0 
     credentials. It should only be executed by the application
     administrator per the app.yaml configuration file.'''

  @login_required
  def get(self):
    # Store empty credentials in the datastore and redirect to main page.

    StorageByKeyName(CredentialsModel, USER_AGENT, 'credentials').locked_put(None)
    self.redirect('/backend/')


ERR_TAG = '<HttpError>'
ERR_END = '</HttpError>'

class PredictAPI(webapp2.RequestHandler):
  '''This class handles Ajax prediction requests, i.e. not user initiated
     web sessions but remote procedure calls initiated from the Javascript
     client code running the browser.
  '''

  def get(self):
    try:
      # Read server-side OAuth 2.0 credentials from datastore and
      # raise an exception if credentials not found.
      credentials = StorageByKeyName(CredentialsModel, USER_AGENT, 
                                    'credentials').locked_get()
      logging.info(credentials)
      if not credentials or credentials.invalid:
        raise Exception('missing OAuth 2.0 credentials')

      # Authorize HTTP session with server credentials and obtain  
      # access to prediction API client library.
      http = credentials.authorize(httplib2.Http())
      service = build('prediction', 'v1.5', http=http)
      papi = service.trainedmodels()
    
      # Build prediction data (csvInstance) dynamically based on form input.
      vals = []
      body = {'input' : {'csvInstance' : [str(self.request.get('text'))] }}

      # Make a prediction and return JSON results to Javascript client.
      ret = papi.predict(id='bullying', body=body).execute() 
      self.response.headers['Content-Type'] = 'application/javascript'
      self.response.out.write(json.dumps(ret))

    except Exception, err:
      # Capture any API errors here and pass response from API back to
      # Javascript client embedded in a special error indication tag.
      err_str = str(err)
      if err_str[0:len(ERR_TAG)] != ERR_TAG:
        err_str = ERR_TAG + err_str + ERR_END
      self.response.out.write(err_str)

app = webapp2.WSGIApplication([
    webapp2.Route(r'/backend/reset/', handler=Reset),
    webapp2.Route(r'/backend/auth_return', handler=AuthHandler),
    webapp2.Route(r'/backend/predict', handler=PredictAPI),
    webapp2.Route(r'/backend/', BackendPage)],debug=True)